package org.wikipedia.database.column; import android.content.ContentValues; import android.database.Cursor; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.text.StrMatcher; import org.apache.commons.lang3.text.StrTokenizer; import java.util.ArrayList; import java.util.Collection; // TODO: replace with table constraints when the database layer is more flexible. public abstract class CsvColumn<T> extends Column<T> { public CsvColumn(@NonNull String tbl, @NonNull String name, @NonNull String type) { super(tbl, name, type); } @Override public T val(@NonNull Cursor cursor) { return val(split(getString(cursor))); } public void put(@NonNull ContentValues values, @NonNull T row) { values.put(getName(), join(put(row))); } @NonNull protected abstract T val(@NonNull Collection<String> strs); @NonNull protected abstract Collection<String> put(@NonNull T row); private String join(@NonNull Collection<String> strs) { StringBuilder builder = new StringBuilder(); for (String str : strs) { builder.append(StringEscapeUtils.escapeCsv(str)); builder.append(','); } if (builder.length() > 0) { builder.deleteCharAt(builder.length() - 1); } return builder.toString(); } @NonNull private Collection<String> split(@Nullable String str) { Collection<String> strs = new ArrayList<>(); for (String escapedStr : tokenizer(str).getTokenList()) { strs.add(StringEscapeUtils.unescapeCsv(escapedStr)); } return strs; } @NonNull private StrTokenizer tokenizer(@Nullable String str) { StrTokenizer tokenizer = StrTokenizer.getCSVInstance(str); tokenizer.setTrimmerMatcher(StrMatcher.noneMatcher()); tokenizer.setEmptyTokenAsNull(true); return tokenizer; } }